﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Data;
using System.Data.SqlClient;
using System.Data.Common;


namespace Ruandao.MiniAccess.SqlServer
{

    /// <summary>
    /// 处理数据库的工具类。
    /// </summary>
    /// <remarks>
    /// 主要方法有：<list type="bullet">
    /// <item><description>void ResetTestData(string dbname, string dbProjectPath, string batchFile)</description></item>
    /// </list>
    /// </remarks>
    public class DbUtility
    {
        #region 创建数据库和运行脚本的实用工具

        /// <summary>
        /// 重新设置测试用的数据库以及测试数据。
        /// <br/>异常：ArgumentException(数据库名为空、路径目录和批处理文件不存在)
        /// </summary>
        /// <param name="dbname">数据库的名称。</param>
        /// <param name="dbProjectPath">数据库项目的全路径名称。</param>
        /// <param name="batchFile">执行删除和插入测试记录的批处理文件的名称。</param>
        /// <param name="connstr">登录数据库实例的链接字符串，一般是登录到master系统数据库。</param>
        /// <remarks>
        /// <list type="bullet">小窍门：
        /// <item>
        /// 在批处理文件中使用 %~dp0 来表示批处理文件所在路径，以"\"结尾。</item>
        /// <item>
        /// 在测试中通过TestContext.TestDir可以用相对路径定位同一个解决方案下面的数据库项目的路径。</item>
        /// </list>
        /// <para>
        /// 如果指定数据库尚未存在，必须数据库项目路径下存在名称为"CreateDb.bat"
        /// 的批处理文件，该批处理接受一个参数(%1)表示要建的数据库名称。</para>
        /// </remarks>
        public static void RunBatchFile(string dbname, string dbProjectPath, string batchFile, string connstr)
        {
            if (string.IsNullOrEmpty(dbname))
            {
                throw new ArgumentException("数据库名称不能为空。","dbname");
            }

            if (!Directory.Exists(dbProjectPath))
            {
                throw new ArgumentException("指定的目录\"" + dbProjectPath + "\"不存在。","dbProjectPath");
            }

            if (!File.Exists(Path.Combine(dbProjectPath, batchFile)))
            {
                throw new ArgumentException("指定的批处理文件\"" + batchFile + "\"不存在。", "dbname");
            }

            if (!ExistDb(dbname, connstr))
            {
                Process createdb = new Process();
                createdb.StartInfo.FileName = Path.Combine(dbProjectPath, "CreateDb.bat");
                createdb.StartInfo.Arguments = dbname;
                createdb.Start();
                createdb.WaitForExit();
            }

            Process batchProcess = new Process();

            batchProcess.StartInfo.FileName = Path.Combine(dbProjectPath, batchFile);
            batchProcess.StartInfo.Arguments = dbname;
            batchProcess.Start();
            batchProcess.WaitForExit();
        }

        /// <summary>
        /// 获取创建数据库的日期。
        /// <br/>异常：SqlException, ArgumentException(指定的数据库不存在。)
        /// </summary>
        /// <param name="dbname">数据库名称。</param>
        /// <param name="connstr">登录数据库实例的链接字符串，一般是登录到master系统数据库。</param>
        /// <returns>保存在数据库中的数据库创建日期。</returns>
        public static DateTime DbCreateDate(string dbname, string connstr)
        {
            string createDate = null;
            SqlConnection con = null;
            SqlDataReader reader = null;
            try
            {
                con = new SqlConnection(connstr);
                SqlCommand cmd = new SqlCommand("sp_helpdb", con);
                cmd.CommandType = CommandType.StoredProcedure;
                con.Open();
                reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                while (reader.Read())
                {
                    if ((string)reader["name"] == dbname)
                    {
                        createDate = (string)reader["created"];
                        break;
                    }
                }
            }
            finally
            {
                if (reader != null) reader.Close();
                if (con != null) con.Close();
            }
            if (createDate == null)
            {
                throw new ArgumentException("本地主机的SQL Server实例中不存在指定的数据库。", dbname);
            }
            return DateTime.Parse(createDate.Substring(6) + "-" + createDate.Substring(0, 2) + "-" + createDate.Substring(3, 2));
        }

        /// <summary>
        /// 获取数据库的数据文件的日期时间。
        /// <br/>异常：SqlException, ArgumentException(指定的数据库不存在), PlatformNotSupportedException(只支持Windows2000以上OS)
        /// </summary>
        /// <param name="dbname">数据库名称。</param>
        /// <param name="connstr">登录数据库实例的链接字符串，一般是登录到master系统数据库。</param>
        /// <returns>数据库文件的创建日期时间</returns>
        public static DateTime DbCreateFileDateTime(string dbname, string connstr)
        {
            SqlConnection con = null;
            SqlDataReader reader = null;
            string firstFile = null;

            try
            {
                con = new SqlConnection(connstr);
                SqlCommand cmd = new SqlCommand("sp_helpdb @dbname='" + dbname + "'", con);
                cmd.CommandType = CommandType.Text;
                con.Open();
                reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                if (reader.NextResult())
                {
                    while (reader.Read())
                    {
                        firstFile = (string)reader["filename"];
                        break;
                    }
                }
            }
            finally
            {
                if (reader != null) reader.Close();
                if (con != null) con.Close();
            }

            if (firstFile == null)
            {
                throw new ArgumentException("本地主机的SQL Server实例中不存在指定的数据库。", dbname);
            }

            return new FileInfo(firstFile).CreationTime;
        }

        /// <summary>
        /// 在本地主机的SqlServer 实例中检查指定数据库是否存在。
        /// 异常：SqlException, PlatformNotSupportedException(不支持Win9x/WinMe)
        /// </summary>
        /// <param name="dbname">数据库名称。</param>
        /// <param name="connstr">登录数据库实例的链接字符串，一般是登录到master系统数据库。</param>
        /// <returns>true: 数据库存在， false: 数据库不存在。 </returns>
        public static bool ExistDb(string dbname, string connstr)
        {
            SqlConnection con = null;
            SqlDataReader reader = null;
            bool exist = false;
            try
            {
                con = new SqlConnection(connstr);
                SqlCommand cmd = new SqlCommand("sp_helpdb", con);
                cmd.CommandType = CommandType.StoredProcedure;
                con.Open();
                reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                while (reader.Read())
                {
                    if ((string)reader["name"] == dbname)
                    {
                        exist = true;
                        break;
                    }
                }
            }
            finally
            {
                if (reader != null) reader.Close();
                if (con != null) con.Close();
            }
            return exist;
        }

        #endregion

        #region 检查数据库中的记录及数据

        /// <summary>
        /// 
        /// </summary>
        /// <param name="database"></param>
        /// <param name="tableName"></param>
        /// <returns></returns>
        public static int GetRecordNumber(Database database, string tableName)
        {
            string sql = string.Format("select count(*) from {0}", tableName);

            return (int)(database.ExecuteScalar(CommandType.Text, sql));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="database"></param>
        /// <param name="tableName"></param>
        /// <param name="keyDictionary"></param>
        /// <param name="valueDictionary"></param>
        /// <returns></returns>
        public static string CheckRecord(SqlDatabase database, string tableName,
                IDictionary<string, object> keyDictionary,
                IDictionary<string, object> valueDictionary)
        {
            TableMap tableSchema = new SqlServerTableMapGenerator(database).GetTableMapFromDb(tableName); 

            string commandText = string.Format("Select {0} from {1}  where {2}",
                GetSelectColumns(valueDictionary.Keys), tableName,
                GetWhereCondition(keyDictionary.Keys));
            DbCommand command = database.GetCommand(commandText);
            foreach (string key in keyDictionary.Keys)
            {
                database.AddInParameter(command, key, tableSchema.GetColumn(key).DbType, keyDictionary[key]);
            }

            string emptyIsOk = "";
            using (IDataReader reader = database.ExecuteReader(command))
            {
                if (!reader.Read()) return "ERROR: 没有找到记录。";

                foreach (string column in valueDictionary.Keys)
                {
                    // 如果不转换类型则即使数据一样比较结果也是不一样，另外转换后object的比较也不是针对数据本身
                    // 所以只能粗略的比较一下tostring（）结果。
                    // 这样的比较对于binary、image等类型来说是不准确的，因为这些类型的tostring（）只是简单的显示类型名。
                    if ( valueDictionary[column].ToString() != reader[column].ToString() )
                    {
                        emptyIsOk += string.Format("\n{0}字段的值应为{1}，实际为{2}", column, valueDictionary[column], reader[column]);
                    }
                }
            } // end of  using reade

            return emptyIsOk;
        }

        // 获取根据主键查找记录的Where语句表达式。
        private static string GetWhereCondition(ICollection<string> keys)
        {
            string condition = "";
            foreach (string key in keys)
            {
                condition += key + "=@" + key + " AND ";
            }
            condition = condition.Substring(0, condition.Length - 5);
            return condition;
        }

        // 根据列名称集合获取Select语句中的字段列表。
        private static string GetSelectColumns(ICollection<string> columns)
        {
            string select = "";
            foreach (string column in columns)
            {
                select += column + ",";
            }
            select = select.Substring(0, select.Length - 1);
            return select;
        }

        #endregion
    }
}
